home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / smaltalk / st80_r41.lha / st80_r41 / usa.4.1.st < prev   
Text File  |  1993-07-23  |  24KB  |  795 lines

  1. "       NAME        usa.4.1.st
  2.         AUTHOR        benson@siemens.siemens.com
  3.         CONTRIBUTOR    Dan Benson <benson@siemens.siemens.com>
  4.         FUNCTION    Map of 50 United States with Browser
  5.         ST-VERSIONS    ST-80 R4.1
  6.         PREREQUISITES    usa.data (from uiuc/st80_r4/usa)
  7.         CONFLICTS
  8.         DISTRIBUTION    world
  9.         VERSION        2
  10.         DATE        September 11, 1992
  11.         SUMMARY
  12.  
  13. This is a simple graphics application that displays a scrolling map  
  14. of the 50 United States along with a scrolling list of their names.  
  15. The user can click directly on a state to highlight it or can choose  
  16. from the list of names. It is obviously very simple but with a little  
  17. imagination it can be turned into something more sophisticated.
  18.  
  19. It requires an ASCII data file (usa.data) to be read in. This file
  20. defines the boundaries of the 50 states as polygons. See the State
  21. class method called readStates to make sure the filename is correct.
  22. After filing in the usa.st file, you should initialize the class
  23. variables in the State class (readStates) and then you can open a
  24. browser (StateBrowser | open).
  25.  
  26. I got the outlines for the states from a public domain MacDraw file.  
  27. When printing a MacDraw file you can choose to print it to a file.  
  28. This saves it as a PostScript file. I then edited the file to extract  
  29. the polygons and manually entered each name. I then read the file in  
  30. Smalltalk to create Smalltalk objects (flipping the x and y  
  31. coordinates). To make a long story short, the polygons are now  
  32. available as Smalltalk objects.
  33.  
  34. NOTE: This filein defines the following category and classes:
  35. ('USA' #Boundary #State #StateBrowser #StateView #StateViewController)
  36. and adds the following instance methods:
  37. ExternalReadString | nextLine
  38. Point | quadrantContaining
  39.  
  40.  
  41. Enjoy.
  42.  
  43. Dan Benson
  44. Siemens Corportate Research
  45. 755 College Road East
  46. Princeton, NJ 08540
  47. benson@siemens.siemens.com
  48. "!
  49.  
  50. Object subclass: #Boundary
  51.     instanceVariableNames: 'vertices boundingBox area '
  52.     classVariableNames: ''
  53.     poolDictionaries: ''
  54.     category: 'USA'!
  55.  
  56.  
  57. !Boundary methodsFor: 'accessing'!
  58.  
  59. area
  60.     area isNil ifTrue: [self computeArea].
  61.     ^area!
  62.  
  63. boundingBox
  64.     boundingBox isNil ifTrue: [self computeBoundingBox].
  65.     ^boundingBox!
  66.  
  67. vertices
  68.     ^vertices!
  69.  
  70. vertices: aCollectionOfPoints
  71.     vertices := aCollectionOfPoints.
  72.     boundingBox := nil.
  73.     area := nil.! !
  74.  
  75. !Boundary methodsFor: 'spatial tests'!
  76.  
  77. containsPoint: aPoint
  78.     "Answer true or false as to whether the receiver contains  
  79. aPoint on its boundary or in its interior.  Uses the winding  
  80. technique.  See the method Point|quadrantContaining. "
  81.     | wind lastPoint oldQuad newQuad |
  82.  
  83.     (self boundingBox containsPoint: aPoint) ifFalse: [^false].
  84.  
  85.     wind := 0.
  86.     lastPoint := vertices last.
  87.     oldQuad := lastPoint quadrantContaining: aPoint.
  88.     vertices do: [:each |
  89.         aPoint = each ifTrue: [^true].
  90.         newQuad := each quadrantContaining: aPoint.
  91.         oldQuad = newQuad
  92.             ifFalse: [oldQuad+1\\4 = newQuad
  93.                         ifTrue: [wind := wind  
  94. + 1]
  95.                         ifFalse:  
  96. [newQuad+1\\4 = oldQuad
  97.                                  
  98.     ifTrue: [wind := wind - 1]
  99.                                  
  100.     ifFalse: [| a b |
  101.                                  
  102.             a := lastPoint y - each y.
  103.                                  
  104.             a := a * (aPoint x - lastPoint x).
  105.                                  
  106.             b := lastPoint x - each x.
  107.                                  
  108.             a := a + (b * lastPoint y).
  109.                                  
  110.             b := b * aPoint y.
  111.                                  
  112.             a > b
  113.                                  
  114.                 ifTrue: [wind := wind - 2]
  115.                                  
  116.                 ifFalse: [a = b ifTrue:[^true]  
  117. ifFalse: [wind := wind + 2]]]]].
  118.         oldQuad := newQuad.
  119.         lastPoint := each].
  120.     ^(wind = 0) not! !
  121.  
  122. !Boundary methodsFor: 'displaying'!
  123.  
  124. displayOn: aGC scale: aScale insideColor: anInsideColorOrNil  
  125. borderColor: aBorderColorOrNil
  126.     | pts |
  127.     pts := self vertices collect: [:p | p * aScale].
  128.     anInsideColorOrNil notNil
  129.         ifTrue: [aGC paint: anInsideColorOrNil;
  130.                     displayPolygon: pts ].
  131.     aBorderColorOrNil notNil
  132.         ifTrue: [aGC paint: aBorderColorOrNil;
  133.                     displayPolyline: pts ].! !
  134.  
  135. !Boundary methodsFor: 'private'!
  136.  
  137. computeArea
  138.     "See page 513 of Foley and Van Dam's Fundamentals of Interactive Computer Graphics.
  139.     The absolute value is taken as the area since it depends on the ordering of the vertices,
  140.     either clockwise or counter-clockwise.
  141.  
  142.     Calculate the area of the receiver defined as:
  143.  
  144.                   n
  145.                 ----
  146.              1  \
  147.        C =   -   \   (y  +  y  )  (x  -  x )
  148.              2   /      i      i@1    i@1   i
  149.                 /
  150.                 ---- 
  151.                  i=1 
  152.        where the operator @ is normal addition except that n@1 = 1.
  153.     "
  154.     area := 0.
  155.     self hasVertices
  156.         ifTrue: [1 to: vertices size-1 do: [:i | area := area + (((vertices at: i) y + (vertices at: i+1) y) * ((vertices at: i+1) x - (vertices at: i) x))].
  157.                 area := (area + ((vertices last y + vertices first y) * (vertices first x - vertices last x))) abs / 2].!
  158.  
  159. computeBoundingBox
  160.     "This is implemented for any number of points.  Points are  
  161. assumed to be in absolute coordinates."
  162.     | minPoint maxPoint |
  163.     boundingBox := self hasVertices
  164.         ifFalse: [Rectangle origin: Point zero corner: Point  
  165. zero]
  166.         ifTrue: [minPoint := self vertices first.
  167.                 maxPoint := self vertices first.
  168.                 self vertices do: [:each |
  169.                     minPoint := minPoint min:  
  170. each.
  171.                     maxPoint := maxPoint max:  
  172. each].
  173.                 Rectangle origin: minPoint corner:  
  174. maxPoint]!
  175.  
  176. hasVertices
  177.     ^(self vertices notNil and: [self vertices isEmpty not])! !
  178.  
  179. !Boundary methodsFor: 'to file'!
  180.  
  181. toFile: aFileStream
  182.     "Write the contents of the receiver to a FileStream.  The  
  183. order of attributes should be the same as the class fromFile:  
  184. method."
  185.  
  186.     aFileStream nextPutAll: 'BOUNDARY'; cr.
  187.     self vertices do: [:p | aFileStream nextPutAll: p x  
  188. printString; nextPut: $ ; nextPutAll: p y printString; cr].
  189.     aFileStream nextPutAll: 'END'; cr! !
  190. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  191.  
  192. Boundary class
  193.     instanceVariableNames: ''!
  194.  
  195.  
  196. !Boundary class methodsFor: 'instance creation'!
  197.  
  198. fromFile: aFileStream
  199.     "Answer an instance of the receiver read from aFileStream."
  200.     | pts i line|
  201.     (aFileStream nextLine = 'BOUNDARY') ifFalse: [self error:  
  202. 'something is wrong.'].
  203.     pts := OrderedCollection new.
  204.     [(line := aFileStream nextLine) = 'END'] whileFalse: [
  205.         i := line indexOf: $ .
  206.         pts add: (Point x: ((line copyFrom: 1 to: i)  
  207. asNumber) y: ((line copyFrom: i+1 to: line size) asNumber))].
  208.     ^self vertices: pts!
  209.  
  210. fromFile: aFileStream new: junk
  211.     "Answer an instance of the receiver read from aFileStream."
  212.     | pts i line|
  213.     (aFileStream nextLine = 'BOUNDARY') ifFalse: [self error:  
  214. 'something is wrong.'].
  215.     pts := OrderedCollection new.
  216.     [(line := aFileStream nextLine) = 'END'] whileFalse: [
  217.         i := line indexOf: $ .
  218.         pts add: (Point x: ((line copyFrom: i+1 to: line  
  219. size) asNumber) y: ((line copyFrom: 1 to: i) asNumber))].
  220.     ^self vertices: pts!
  221.  
  222. fromFile: aFileStream save: junk
  223.     "Answer an instance of the receiver read from aFileStream."
  224.     | pts i line|
  225.     (aFileStream nextLine = 'BOUNDARY') ifFalse: [self error:  
  226. 'something is wrong.'].
  227.     pts := OrderedCollection new.
  228.     [(line := aFileStream nextLine) = 'END'] whileFalse: [
  229.         i := line indexOf: $ .
  230.         pts add: (Point x: ((line copyFrom: 1 to: i)  
  231. asNumber) y: ((line copyFrom: i+1 to: line size) asNumber))].
  232.     ^self vertices: pts!
  233.  
  234. vertices: aCollectionOfPoints
  235.     ^self new vertices: aCollectionOfPoints! !
  236.  
  237. AutoScrollingView subclass: #StateView
  238.     instanceVariableNames: 'background scale '
  239.     classVariableNames: ''
  240.     poolDictionaries: ''
  241.     category: 'USA'!
  242.  
  243.  
  244. !StateView methodsFor: 'updating'!
  245.  
  246. update: anAspect with: anObject
  247.     #currentState == anAspect
  248.         ifTrue: [self displayStates: anObject on: self graphicsContext]! !
  249.  
  250. !StateView methodsFor: 'menu'!
  251.  
  252. displayMenu
  253.     "Answer the receiver's own display menu (independent of  
  254. model)."
  255.     ^PopUpMenu
  256.         labels: ' change scale'
  257.         lines: #()
  258.         values: #(changeScale)!
  259.  
  260. localMenuItem: selector 
  261.  
  262.     "Answer whether selector is one of the operate button menu  
  263. items for the receiver."
  264.     ^#(changeScale) includes: selector!
  265.  
  266. menu
  267.     "Get the receiver's model's menu and tack on the receiver's  
  268. display menu."
  269.     | topMenu displayMenu allLabels allLines allValues|
  270.     (topMenu := self model menu) isNil
  271.         ifTrue: [^self displayMenu].
  272.     displayMenu := self displayMenu.
  273.     allLabels := ' '.
  274.     topMenu labels do: [:str | allLabels := allLabels, str, '\'].
  275.     allLabels := allLabels, ' display '.
  276.     allLines := Array new: topMenu lines size + 1.
  277.     1 to: topMenu lines size do: [:i | allLines at: i put:  
  278. (topMenu lines at: i)].
  279.     allLines at: allLines size put: topMenu numberOfItems.
  280.     allValues := Array new: topMenu numberOfItems + 1.
  281.     1 to: topMenu numberOfItems do: [:i | allValues at: i put:  
  282. (topMenu values at: i)].
  283.     allValues at: topMenu numberOfItems + 1 put: displayMenu.
  284.     ^PopUpMenu
  285.         labels: allLabels withCRs
  286.         lines: allLines
  287.         values: allValues! !
  288.  
  289. !StateView methodsFor: 'scrolling'!
  290.  
  291. grid: aPoint
  292.     "Use a method to access the inherited instance variable. This method allows the receiver to modify the scroll grid."
  293.     scrollOffset grid: aPoint!
  294.  
  295. offset
  296.     "Use a method to access the inherited instance variable."
  297.     ^scrollOffset value!
  298.  
  299. preferredBounds
  300.     ^self model bounds scaledBy: self displayScale! !
  301.  
  302. !StateView methodsFor: 'scaling'!
  303.  
  304. changeScale
  305.     | answer |
  306.     answer := DialogView
  307.         request: 'New scale (current = ', self scale  
  308. printString, '%) ?'
  309.         initialAnswer: self scale printString.
  310.     (answer isNil or: [answer isEmpty])
  311.         ifFalse: [self scale: answer asNumber]!
  312.  
  313. displayScale
  314.     "Answer the screen scale factor, calculated from the  
  315. percentage."
  316.     ^(self scale / 100) asPoint!
  317.  
  318. maxScale
  319.     ^300!
  320.  
  321. minScale
  322.     ^5!
  323.  
  324. scale
  325.     ^scale!
  326.  
  327. scale: aNumber
  328.     scale := (aNumber abs min: self maxScale) max: self minScale.
  329. "Note: the grid is changed to reflect the scale such that it's one tenth the model's extent. If this is not acceptable it can be modified or eliminated here."
  330.     self grid: (self preferredBounds extent * self displayScale/10) rounded.
  331. "First, the view is reset and then it refreshes itself. The reason for this is that if the view was already positioned at 0@0 and was rescaled, the view would not be automatically refreshed."
  332.     self positionTo: 0@0;
  333.         invalidateRectangle: self bounds repairNow: true! !
  334.  
  335. !StateView methodsFor: 'displaying'!
  336.  
  337. displayBackgroundOn: aGC
  338.     aGC paint: self waterColor;
  339.         displayRectangle: self bounds!
  340.  
  341. displayOn: aGC
  342.     self displayBackgroundOn: aGC.
  343.     self displayStates: self model states on: aGC.!
  344.  
  345. displayStates: aCollectionOfStates on: aGC
  346.     | bb|
  347.     bb := aGC clippingBounds scaledBy: self displayScale reciprocal.
  348.     aCollectionOfStates do: [:o | 
  349.         (o notNil and: [o boundingBox intersects: bb])
  350.             ifTrue: [o  displayOn: aGC scale: self displayScale]].!
  351.  
  352. waterColor
  353.     ^ColorValue blue! !
  354.  
  355. !StateView methodsFor: 'private'!
  356.  
  357. model: aModel
  358.     super model: aModel.
  359.     scale := 25! !
  360.  
  361. !StateView methodsFor: 'controller accessing'!
  362.  
  363. defaultControllerClass
  364.     ^StateViewController! !
  365. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  366.  
  367. StateView class
  368.     instanceVariableNames: ''!
  369.  
  370.  
  371. !StateView class methodsFor: 'instance creation'!
  372.  
  373. on: aModel wholeMsg: aWholeMsg wholeDisplayMsg: aWholeDisplayMsg  
  374. partMsg: aPartMsg partDisplayMsg: aPartDisplayMsg menu: aMenuMsg  
  375. backgroundMsg: aBgMsg backgroundDisplayMsg: aBgDisplayMsg
  376.     "Create a 'pluggable' (see class comment) GraphicsView viewing aModel.
  377.     aspectMsg is sent to read the current text value in the model.
  378.     It is also used as the changed: parameter for this view.
  379.     changeMsg is sent to inform aModel of new text for the model.
  380.     menuMsg is sent to read the operate button menu for this view."
  381.  
  382.     ^ self new on: aModel wholeMsg: aWholeMsg wholeDisplayMsg:  
  383. aWholeDisplayMsg partMsg: aPartMsg partDisplayMsg: aPartDisplayMsg  
  384. menu: aMenuMsg backgroundMsg: aBgMsg backgroundDisplayMsg:  
  385. aBgDisplayMsg! !
  386.  
  387. ControllerWithMenu subclass: #StateViewController
  388.     instanceVariableNames: 'savedArea gc savedLineWidth savedPaint mouse '
  389.     classVariableNames: ''
  390.     poolDictionaries: ''
  391.     category: 'USA'!
  392.  
  393.  
  394. !StateViewController methodsFor: 'control defaults'!
  395.  
  396. redButtonActivity
  397.     self model selectAt: self sensor cursorPoint  / self view displayScale!
  398.  
  399. yellowButtonActivity
  400.     "Determine which item in the operate button menu is selected.  
  401. If one is selected, then send my model the corresponding message."
  402.     | selector menu |
  403.     (menu := self view menu) notNil
  404.         ifTrue: [selector := menu startUp.
  405.                 selector ~= 0
  406.                     ifTrue: [(self view localMenuItem: selector)
  407.                         ifTrue: [self view perform: selector]
  408.                         ifFalse: [self model perform: selector]]]
  409.         ifFalse: [super controlActivity]! !
  410.  
  411. Object subclass: #State
  412.     instanceVariableNames: 'name boundaries insideColor borderColor '
  413.     classVariableNames: 'USA USABounds '
  414.     poolDictionaries: ''
  415.     category: 'USA'!
  416.  
  417.  
  418. !State methodsFor: 'accessing'!
  419.  
  420. addBoundary: aBoundary
  421.     self hasBoundaries ifFalse: [boundaries := OrderedCollection  
  422. new].
  423.     self boundaries add: aBoundary!
  424.  
  425. area
  426.     ^self hasBoundaries
  427.         ifTrue: [self boundaries inject: 0 into: [:a :b | a +  
  428. b area]]
  429.         ifFalse: [0]!
  430.  
  431. borderColor
  432.     ^borderColor!
  433.  
  434. borderColor: aColorValue
  435.     borderColor := aColorValue!
  436.  
  437. boundaries
  438.     ^boundaries!
  439.  
  440. boundaries: aCollectionOfBoundaries
  441.     boundaries := aCollectionOfBoundaries!
  442.  
  443. boundingBox
  444.     "Answer the receiver's total bounding box"
  445.     ^self hasBoundaries
  446.         ifFalse: [Rectangle origin: 0@0 corner: 0@0]
  447.         ifTrue:    [| bb |
  448.                 bb := boundaries first boundingBox.
  449.                 boundaries inject: bb into: [:rect  
  450. :bounds | rect merge: (bounds boundingBox)]]!
  451.  
  452. hasBoundaries
  453.     ^self boundaries notNil and: [self boundaries isEmpty not]!
  454.  
  455. insideColor
  456.     ^insideColor!
  457.  
  458. insideColor: aColorValue
  459.     insideColor := aColorValue!
  460.  
  461. name
  462.     ^name!
  463.  
  464. name: aString
  465.     name := aString! !
  466.  
  467. !State methodsFor: 'displaying'!
  468.  
  469. displayOn: aGC
  470.     self hasBoundaries ifTrue: [
  471.         self boundaries do: [:b | b displayOn: aGC  
  472. insideColor: self insideColor borderColor: self borderColor]]!
  473.  
  474. displayOn: aGC scale: aScale
  475.     self hasBoundaries ifTrue: [
  476.         self boundaries do: [:b | b displayOn: aGC scale:  
  477. aScale insideColor: self insideColor borderColor: self borderColor]]! !
  478.  
  479. !State methodsFor: 'spatial tests'!
  480.  
  481. containedInRectangle: aRectangle
  482.     "Answer true if the reciever is wholly contained in  
  483. aRectangle."
  484.     ^self hasBoundaries
  485.         ifTrue: [aRectangle contains: self boundingBox]
  486.         ifFalse: [false]!
  487.  
  488. containsPoint: aPoint
  489.     "Answer true if any of the reciever's boundaries contain aPoint."
  490.     self hasBoundaries
  491.         ifTrue: [self boundaries do: [:b | (b containsPoint: aPoint) ifTrue: [^true]]].
  492.     ^false! !
  493.  
  494. !State methodsFor: 'to file'!
  495.  
  496. toFile: aFileStream
  497.     "Write the contents of the receiver to a FileStream.  The  
  498. order of attributes should be the same as the class fromFile:  
  499. method."
  500.  
  501.     aFileStream
  502.         nextPutAll: 'STATE'; cr;
  503.         nextPutAll: self name; cr;
  504.         nextPutAll: self boundaries size printString; cr.
  505.     self boundaries do: [:b | b toFile: aFileStream].
  506.     aFileStream nextPutAll: 'END'; cr.! !
  507. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  508.  
  509. State class
  510.     instanceVariableNames: ''!
  511.  
  512.  
  513. !State class methodsFor: 'instance creation'!
  514.  
  515. fromFile: aFileStream
  516.     "Answer an instance of the receiver read from aFileStream."
  517.     | aState line |
  518.     aState := self new.
  519.     [(line := aFileStream nextLine) = 'END'] whileFalse: [
  520.         aState name: line.
  521.         aFileStream nextLine asNumber timesRepeat: [aState  
  522. addBoundary: (Boundary fromFile: aFileStream)]].
  523.     ^aState! !
  524.  
  525. !State class methodsFor: 'initialize'!
  526.  
  527. readStates
  528.     "State readStates"
  529.     | fs |
  530.     fs := 'usa.data' asFilename readStream.
  531.     USA := SortedCollection sortBlock: [:a :b | a name < b name].
  532.     [fs nextLine = 'STATE'] whileTrue: [| st |
  533.         USA add: (st := State fromFile: fs).
  534.         Transcript show: 'just read ', st name; cr].
  535.     fs close.
  536.     USABounds := 0@0 corner: (USA inject: USA first boundingBox  
  537. into: [:bb : st | bb merge: st boundingBox]) corner!
  538.  
  539. writeStates
  540.     "State writeStates"
  541.     | fs |
  542.     fs := 'usa.data' asFilename writeStream.
  543.     USA do: [:st | st toFile: fs].
  544.     fs close! !
  545.  
  546. !State class methodsFor: 'accessing'!
  547.  
  548. usa
  549.     ^USA isNil
  550.         ifTrue: [self error: 'USA has not been loaded!!!! See  
  551. State class method readStates']
  552.         ifFalse: [USA]!
  553.  
  554. usaBounds
  555.     ^USABounds! !
  556.  
  557. Model subclass: #StateBrowser
  558.     instanceVariableNames: 'states currentState '
  559.     classVariableNames: ''
  560.     poolDictionaries: ''
  561.     category: 'USA'!
  562. StateBrowser comment:
  563. 'The browser presents two views, an alphabetical list of the state names and a graphical map of the 50 United States. The user can select an individual state by clicking on its name in the list or by clicking directly within its border on the map. In either case, the two views are automatically updated such that the selected state is highlighted in both the list and the map.
  564.  
  565. The map display may be scrolled in both vertical and horizontal directions and can also be scaled via a menu selection.
  566.  
  567. ___Technicals___
  568.  
  569. This application was originally written in Objectworks Release 4.0 and has been modified to run under Release 4.1. 
  570.  
  571. This is a simple graphics application that displays a scrolling map of the 50 United States along with a scrolling list of their names. The user can click directly on a state to highlight it or can choose from the list of names. It is obviously very simple but with a little imagination it can be turned into something more sophisticated.
  572.  
  573. It requires an ASCII data file usa.data to be read in. This file defines the boundaries of the 50 states as polygons. See the State class method called readStates to make sure the filename is correct. After filing in the usa.st file, you should initialize the class variables in the State class (readStates) and then you can open a browser (StateBrowser | open).
  574.  
  575. I got the outlines for the states from a public domain MacDraw file. When printing a MacDraw file you can choose to print it to a file. This saves it as a PostScript file. I then edited the file to extract the polygons and manually entered each name. I then read the file in Smalltalk to create Smalltalk objects (flipping the x and y coordinates). To make a long story short, the polygons are now available as Smalltalk objects.
  576.  
  577.  Enjoy.
  578.  
  579. Dan Benson
  580. Siemens Corporate Research
  581. 755 College Road East
  582. Princeton, NJ 08540
  583. benson@siemens.siemens.com
  584. '!
  585.  
  586.  
  587. !StateBrowser methodsFor: 'colors'!
  588.  
  589. selectedStateBorderColor
  590.     ^ColorValue black!
  591.  
  592. selectedStateInsideColor
  593.     ^ColorValue red!
  594.  
  595. stateBorderColor
  596.     ^ColorValue black!
  597.  
  598. stateInsideColor
  599.     ^ColorValue white! !
  600.  
  601. !StateBrowser methodsFor: 'open'!
  602.  
  603. initialize
  604.     states := State usa copy asSortedCollection: [:a :b | a name < b name].
  605.     states do: [:s | s insideColor: self stateInsideColor; borderColor: self stateBorderColor].
  606.     currentState := nil.!
  607.  
  608. nameListView
  609.     | topView oneLine |
  610.     oneLine := TextAttributes default lineGrid + 3.
  611.     topView := DependentComposite new.
  612.     topView
  613.         add: ('      States' withCRs asText allBold  
  614. asComposedText)
  615.         in: (LayoutFrame new
  616.             leftFraction: 0 offset: 5;
  617.             rightFraction: 1 offset: 0;
  618.             topFraction: 0 offset: 5;
  619.             bottomFraction: 0 offset: oneLine);
  620.     add: (LookPreferences edgeDecorator on:
  621.         (SelectionInListView on: self printItems: false  
  622. oneItem: false
  623.             aspect: #newStateList change:  
  624. #selectStateWithName: list: #allStateNames
  625.             menu: nil initialSelection:  
  626. #currentStateName))
  627.         in: (LayoutFrame new
  628.             leftFraction: 0 offset: 0;
  629.             rightFraction: 1 offset: 0;
  630.             topFraction: 0 offset: oneLine;
  631.             bottomFraction: 1 offset: 0).
  632.     ^topView!
  633.  
  634. open
  635.     "StateBrowser new open"
  636.     self openWithListOnLeft: true!
  637.  
  638. openWithListOnLeft: aBoolean
  639.     "Open a ScheduledWindow on the receiver composed of two views, an alphabetical list of state names and a graphical map of the 50 United States. If aBoolean is true, put the name list view on the left, otherwise put it on the right."
  640.     "StateBrowser new openWithListOnLeft: false"
  641.     |  window composite listWidth |
  642.     listWidth := 100.
  643.     window := ScheduledWindow new label: 'USA Browser'.
  644.     window component: (composite := CompositePart new).
  645.     aBoolean
  646.         ifTrue: [composite 
  647.             add: self nameListView
  648.             in: (LayoutFrame new
  649.                 leftFraction: 0 offset: 0;
  650.                 rightFraction: 0 offset: listWidth;
  651.                 topFraction: 0 offset: 0;
  652.                 bottomFraction: 1 offset: 0);
  653.             add: ((LookPreferences edgeDecorator) on: (StateView model: self))
  654.                     useHorizontalScrollBar
  655.             in: (LayoutFrame new 
  656.                 leftFraction: 0 offset: listWidth; 
  657.                 rightFraction: 1 offset: 0; 
  658.                 topFraction: 0 offset: 0; 
  659.                 bottomFraction: 1 offset: 0)]
  660.         ifFalse: [composite 
  661.             add: self nameListView
  662.             in: (LayoutFrame new
  663.                 leftFraction: 1 offset: listWidth negated;
  664.                 rightFraction: 1 offset: 0;
  665.                 topFraction: 0 offset: 0;
  666.                 bottomFraction: 1 offset: 0);
  667.             add: ((LookPreferences edgeDecorator) on: (StateView model: self))
  668.                     useHorizontalScrollBar
  669.             in: (LayoutFrame new 
  670.                 leftFraction: 0 offset: 0; 
  671.                 rightFraction: 1 offset: listWidth negated; 
  672.                 topFraction: 0 offset: 0; 
  673.                 bottomFraction: 1 offset: 0)].
  674.  
  675.     window openWithExtent: 300@200! !
  676.  
  677. !StateBrowser methodsFor: 'selecting'!
  678.  
  679. allStateNames
  680.     ^self states collect: [:s | s name]!
  681.  
  682. currentState
  683.     ^currentState!
  684.  
  685. currentState: aState
  686.     currentState == aState
  687.         ifFalse: [| previousState |
  688.             previousState := currentState.
  689.             previousState notNil
  690.                 ifTrue: [previousState insideColor:  
  691. self stateInsideColor; borderColor: self stateBorderColor].
  692.             currentState := aState.
  693.             currentState notNil
  694.                 ifTrue: [currentState insideColor:  
  695. self selectedStateInsideColor; borderColor: self  
  696. selectedStateBorderColor].
  697.             self changed: #currentState with: (Array  
  698. with: previousState with: currentState);
  699.                 changed: #currentStateName]!
  700.  
  701. currentStateName
  702.     ^self currentState notNil
  703.         ifTrue: [self currentState name]
  704.         ifFalse: [nil]!
  705.  
  706. selectAt: aPoint
  707.     "Select the state that contains aPoint."
  708.     self states do: [:s | (s containsPoint: aPoint)
  709.             ifTrue: [^self currentState: s]]!
  710.  
  711. selectStateWithName: aString
  712.     aString isNil
  713.         ifTrue: [self currentState: nil]
  714.         ifFalse: [self currentState: (self states detect: [:s  
  715. | s name = aString])]! !
  716.  
  717. !StateBrowser methodsFor: 'accessing'!
  718.  
  719. aboutText
  720.     ^ValueHolder with: 
  721. 'The browser presents two views, an alphabetical list of the state names and a graphical map of the 50 United States. The user can select an individual state by clicking on its name in the list or by clicking directly within its border on the map. In either case, the two views are automatically updated such that the selected state is highlighted in both the list and the map.
  722.  
  723. The map display may be scrolled in both vertical and horizontal directions and can also be scaled via a menu selection.
  724.  
  725. This application is a simple graphics example in Smalltalk that was originally written in Objectworks Release 4.0 and has been modified to run under Release 4.1. It was written by:
  726.  
  727. Dan Benson
  728. Siemens Corporate Research
  729. 755 College Road East
  730. Princeton, NJ 08540
  731. benson@siemens.siemens.com
  732. ' asText!
  733.  
  734. aboutUSABrowser
  735.     ComposedTextView open: self aboutText label: 'About USA Browser ...' icon: nil extent: (340@200)!
  736.  
  737. bounds
  738.     ^State usaBounds!
  739.  
  740. menu
  741.     ^PopUpMenu
  742.         labels: ' about USA Browser ... '
  743.         values: #(aboutUSABrowser)!
  744.  
  745. states
  746.     ^states! !
  747. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  748.  
  749. StateBrowser class
  750.     instanceVariableNames: ''!
  751.  
  752.  
  753. !StateBrowser class methodsFor: 'instance creation'!
  754.  
  755. new
  756.     "StateBrowser new open"
  757.     ^super new initialize! !
  758.  
  759. !ExternalReadStream methodsFor: 'usa additions'!
  760.  
  761. nextLine
  762.     "This method fetches the next line of input from the stream."
  763.     | aLine |
  764.     self atEnd | binary
  765.         ifTrue: [^nil].
  766.     aLine := ''.
  767.     [aLine := aLine , (String with: self next).
  768.     (aLine at: aLine size) = LF
  769.         ifTrue: [^aLine copyFrom: 1 to: aLine size - 1].
  770.     (aLine at: aLine size) = CR
  771.         ifTrue: [self peekFor: LF.
  772.                 ^aLine copyFrom: 1 to: aLine size - 1].
  773.     self atEnd not]
  774.         whileTrue: [].
  775.     ^aLine copyFrom: 1 to: aLine size! !
  776.  
  777. !Point methodsFor: 'usa additions'!
  778.  
  779. quadrantContaining: aPoint
  780.     "Answer the number of the quadrant containing aPoint placing  
  781. the receiver at the origin, where the quadrants are numbered as  
  782. follows:
  783.         1  |  0
  784.         ------
  785.         2  |  3
  786.     This convention is used for determining whether a point is in  
  787. a polygon."
  788.     ^aPoint x > x
  789.         ifTrue: [aPoint y >= y
  790.                     ifTrue: [3]    
  791.                     ifFalse: [0]]
  792.         ifFalse: [aPoint y >= y
  793.                     ifTrue: [2]    
  794.                     ifFalse: [1]]! !
  795.